import json
import logging
import re

from django import http
from django.contrib.auth import login, authenticate, logout
from django.contrib.auth.mixins import LoginRequiredMixin
from django.db import DatabaseError
from django.shortcuts import render, redirect
from django.urls import reverse
from django.views import View
from django_redis import get_redis_connection

from celery_tasks.email.tasks import send_verify_email
from goods.models import SKU
from meiduo_mall.utils.response_code import RETCODE
from meiduo_mall.utils.views import LoginRequiredJSONMixin
from users.models import User, Address
from . import constants
from .utils import generate_verify_email, check_verify_email_token

# Create your views here.
logger = logging.getLogger('django')


class UserBrowseHistory(LoginRequiredJSONMixin, View):
    """保存查询用户浏览记录"""

    def get(self, request):
        """查询用户浏览记录"""
        # 获取登陆用户信息
        user = request.user

        # 创建连接到redis对象
        redis_conn = get_redis_connection('history')

        # 取出列表数据
        sku_ids = redis_conn.lrange('history_%s' % user.id, 0, -1)

        # 将模型列表转化为字典列表
        skus = []
        for sku_id in sku_ids:
            sku = SKU.objects.get(id=sku_id)
            skus.append({
                "id": sku.id,
                "name": sku.name,
                "default_image_url": sku.default_image.url,
                "price": sku.price,
            })

        return http.JsonResponse({'code': RETCODE.OK, 'errmsg': 'OK', 'skus': skus})

    def post(self, request):
        """保存用户浏览记录"""
        # 接收参数
        json_dict = json.loads(request.body.decode())
        sku_id = json_dict.get('sku_id')

        # 检验参数
        try:
            SKU.objects.get(id=sku_id)
        except SKU.DoesNotExist:
            return http.HttpResponseForbidden('sku不存在')

        # 保存用户浏览记录
        redis_conn = get_redis_connection('history')
        pl = redis_conn.pipeline()
        user = request.user

        # 先去重
        pl.lrem('history_%s' % user.id, 0, sku_id)
        # 在添加
        pl.lpush('history_%s' % user.id, sku_id)
        # 最后截取
        pl.ltrim('history_%s' % user.id, 0, 4)

        # 执行
        pl.execute()

        # 返回响应
        return http.JsonResponse({'code': RETCODE.OK, 'errmsg': 'OK'})


class ChangePasswordView(LoginRequiredMixin, View):
    """修改用户密码"""

    def get(self, request):
        """展示页面"""
        return render(request, 'user_center_pass.html')

    def post(self, request):
        """修改密码"""
        # 接收参数
        old_password = request.POST.get('old_password')
        new_password = request.POST.get('new_password')
        new_password2 = request.POST.get('new_password2')

        # 校验参数
        if not all([old_password, new_password, new_password2]):
            return http.HttpResponseForbidden('缺少必传参数')

        try:
            # 从数据库中拿出旧密码进行校验比对
            request.user.check_password(old_password)
        except Exception as e:
            logger.error(e)
            return render(request, 'user_center_pass.html', {'origin_pwd_errmsg': '原始密码错误'})

        if not re.match(r'^[0-9A-Za-z]{8,20}$', new_password):
            return http.HttpResponseForbidden('密码最少8位，最长20位')

        if new_password != new_password2:
            return http.HttpResponseForbidden('两次输入的密码不一致')

        # 实现业务主体逻辑
        # 修改密码
        try:
            request.user.set_password(new_password)
            request.user.save()
        except Exception as e:
            logger.error(e)
            return render(request, 'user_center_pass.html', {'change_pwd_errmsg': '修改密码失败'})

        # 清理状态保持信息
        logout(request)
        response = redirect(reverse('users:login'))
        response.delete_cookie('username')

        # 返回响应
        return response


class UpdateTitleAddressView(LoginRequiredJSONMixin, View):
    """修改地址标题后端逻辑实现"""

    def put(self, request, address_id):
        # 接收参数
        json_dict = json.loads(request.body.decode())
        title = json_dict.get('title')
        try:
            # 查询当前要更新的标题栏地址
            address = Address.objects.get(id=address_id)

            # 将新的地址标题覆盖原有的地址标题
            address.title = title
            address.save()
        except Exception as e:
            logger.error(e)
            return http.JsonResponse({'code': RETCODE.OK, 'errmsg': '修改标题栏失败'})

        # 返回响应
        return http.JsonResponse({'code': RETCODE.OK, 'errmsg': '修改标题栏成功'})


class DefaultAddressView(LoginRequiredJSONMixin, View):
    """修改默认收货地址"""

    def put(self, request, address_id):
        try:
            # 接收数据 , 查询那个地址是默认的收货地址
            address = Address.objects.get(id=address_id)
            # 将指定的收货地址设置为默认收货地址
            request.user.default_address = address
            request.user.save()
        except Exception as e:
            logger.error(e)
            return http.JsonResponse({'code': RETCODE.DBERR, 'errmsg': '修改默认收货地址失败'})

        return http.JsonResponse({'code': RETCODE.OK, 'errmsg': '修改默认收货地址成功'})


class UpdateDestroyAddressView(LoginRequiredJSONMixin, View):
    """修改删除用户收货地址"""

    def put(self, request, address_id):
        """修改收货地址"""
        # 接收参数
        json_str = request.body.decode()
        json_dict = json.loads(json_str)
        receiver = json_dict.get('receiver')  # 收货人
        province_id = json_dict.get('province_id')  # 城市
        city_id = json_dict.get('city_id')  # 市
        district_id = json_dict.get('district_id')  # 地区
        place = json_dict.get('place')  # 详细地址
        mobile = json_dict.get('mobile')  # 手机号
        tel = json_dict.get('tel')  # 固定电话
        email = json_dict.get('email')  # 邮箱

        # 检验参数
        if not all([receiver, province_id, city_id, district_id, place, mobile]):
            return http.HttpResponseForbidden('缺少必传参数')
        if not re.match(r'^1[3-9]\d{9}$', mobile):
            return http.HttpResponseForbidden('参数mobile有误')
        if tel:
            if not re.match(r'^(0[0-9]{2,3}-)?([2-9][0-9]{6,7})+(-[0-9]{1,4})?$', tel):
                return http.HttpResponseForbidden('参数tel有误')
        if email:
            if not re.match(r'^[a-z0-9][\w\.\-]*@[a-z0-9\-]+(\.[a-z]{2,5}){1,2}$', email):
                return http.HttpResponseForbidden('参数email有误')

        # 判断用户信息是否存在
        try:
            # 使用最新的地址信息（id）,覆盖指定的新的地址信息
            Address.objects.filter(id=address_id).update(
                user=request.user,
                title=receiver,
                receiver=receiver,
                province_id=province_id,
                city_id=city_id,
                district_id=district_id,
                place=place,
                mobile=mobile,
                tel=tel,
                email=email
            )
        except Exception as e:
            logger.error(e)
            return http.JsonResponse({'code': RETCODE.DBERR, 'errmsg': '更新地址失败'})

        # 构造响应
        try:
            address = Address.objects.get(id=address_id)
            address_dict = {
                'id': address.id,
                "title": address.title,
                "receiver": address.receiver,
                "province": address.province.name,
                "city": address.city.name,
                "district": address.district.name,
                "place": address.place,
                "mobile": address.mobile,
                "tel": address.tel,
                "email": address.email
            }
        except Exception as e:
            logger.error(e)
            return http.JsonResponse({'code': RETCODE.DBERR, 'errmsg': '更新地址失败'})

        # 响应新的地址信息给前段渲染
        return http.JsonResponse({'code': RETCODE.OK, 'errmsg': '更新地址成功', 'address': address_dict})

    def delete(self, request, address_id):
        """对用户收货地址进行逻辑删除"""
        try:
            # 查询要删除的地址
            address = Address.objects.get(id=address_id)

            # 将要删除的地址的is_delete字段设置为True
            address.is_deleted = True
            address.save()
        except Exception as e:
            logger.error(e)
            return http.JsonResponse({'code': RETCODE.DBERR, 'errmsg': '删除地址失败'})

        # 响应删除地址结果
        return http.JsonResponse({'code': RETCODE.OK, 'errmsg': '删除地址成功'})


class AddressCreateView(LoginRequiredJSONMixin, View):
    """新增用户地址"""

    def post(self, request):
        """新增用户地址后端逻辑"""
        # 判断地址是否达到上限20个
        # count = request.Address.objects.filter(user=request.user).count()
        count = request.user.addresses.count()  # 一查多，使用related_name查询
        if count >= constants.USER_ADDRESS_COUNTS_LIMIT:
            return http.JsonResponse({'code': RETCODE.THROTTLINGERR, 'errmsg': '超过地址数量上限'})

        # 接收参数
        json_str = request.body.decode()
        json_dict = json.loads(json_str)
        receiver = json_dict.get('receiver')  # 收货人
        province_id = json_dict.get('province_id')  # 城市
        city_id = json_dict.get('city_id')  # 市
        district_id = json_dict.get('district_id')  # 地区
        place = json_dict.get('place')  # 详细地址
        mobile = json_dict.get('mobile')  # 手机号
        tel = json_dict.get('tel')  # 固定电话
        email = json_dict.get('email')  # 邮箱

        # 校验参数
        if not all([receiver, province_id, city_id, district_id, place, mobile]):
            return http.HttpResponseForbidden('缺少必传参数')

        if not re.match(r'^1[3-9]\d{9}$', mobile):
            return http.HttpResponseForbidden('参数mobile有误')

        if tel:
            if not re.match(r'^(0[0-9]{2,3}-)?([2-9][0-9]{6,7})+(-[0-9]{1,4})?$', tel):
                return http.HttpResponseForbidden('参数tel有误')

        if email:
            if not re.match(r'^[a-z0-9][\w\.\-]*@[a-z0-9\-]+(\.[a-z]{2,5}){1,2}$', email):
                return http.HttpResponseForbidden('参数email有误')

        # 实现业务主体逻辑
        # 保存用户传入的地址信息
        try:
            address = Address.objects.create(
                # 绑定当前登陆的用户
                user=request.user,
                title=receiver,
                receiver=receiver,
                province_id=province_id,
                city_id=city_id,
                district_id=district_id,
                place=place,
                mobile=mobile,
                tel=tel,
                email=email
            )

            # 设置默认收货地址
            if not request.user.default_address:
                request.user.default_address = address
                request.user.save()

        except Exception as e:
            logger.error(e)
            return http.JsonResponse({'code': RETCODE.DBERR, 'errmsg': '新增地址失败'})

        # 新增地址成功，将新增的地址响应给前端实现局部刷新
        address_dict = {
            'id': address.id,
            "title": address.title,
            "receiver": address.receiver,
            "province": address.province.name,
            "city": address.city.name,
            "district": address.district.name,
            "place": address.place,
            "mobile": address.mobile,
            "tel": address.tel,
            "email": address.email
        }

        # 返回响应
        return http.JsonResponse({'code': RETCODE.OK, 'errmsg': '新增地址成功', 'address': address_dict})


class AddressView(LoginRequiredMixin, View):
    """用户收货地址"""

    def get(self, request):
        """查询并展示收货地址界面"""
        # 获取当前登陆用户对象
        login_user = request.user

        # 查询到满足is_delete为false的数据
        addresses = Address.objects.filter(user=login_user, is_deleted=False)

        # 将模型列表装换为字典列表
        address_list = []
        for address in addresses:
            address_dict = {
                "id": address.id,
                "title": address.title,
                "receiver": address.receiver,
                "province": address.province.name,
                "city": address.city.name,
                "district": address.district.name,
                "place": address.place,
                "mobile": address.mobile,
                "tel": address.tel,
                "email": address.email
            }
            address_list.append(address_dict)

        # 构造context渲染模板
        context = {
            "default_address_id": login_user.default_address_id,
            "addresses": address_list,
        }

        return render(request, 'user_center_site.html', context)


class VerifyEmailView(View):
    """验证邮箱"""

    def get(self, request):
        """实现邮箱验证逻辑"""
        # 接收参数
        token = request.GET.get('token')

        # 检验参数
        if not token:
            return http.HttpResponseBadRequest('缺少token')

        # 将token反序列化获取user 从token中提取user.id=>user(反序列化)
        user = check_verify_email_token(token)

        if not user:
            return http.HttpResponseForbidden('无效的token')
        try:
            # 将用户的email_active的值设置为True
            user.email_active = True
            user.save()
        except Exception as e:
            logger.error(e)
            return http.HttpResponseServerError('邮箱验证失败')
        # 返回响应结果
        return redirect(reverse('users:info'))


class EmailView(LoginRequiredJSONMixin, View):
    """发送邮箱验证"""

    def put(self, request):
        # 接收数据
        json_dict = json.loads(request.body.decode())
        email = json_dict.get('email')

        # 校验参数
        if not email:
            return http.HttpResponseForbidden('缺少email参数')

        if not re.match(r'^[a-z0-9][\w\.\-]*@[a-z0-9\-]+(\.[a-z]{2,5}){1,2}$', email):
            return http.HttpResponseForbidden('参数email有误')

        # 将用户保存的邮箱添加到用户数据库的email字段中
        try:
            request.user.email = email
            request.user.save()
        except Exception as e:
            logger.error(e)
            return http.JsonResponse({'code': RETCODE.DBERR, 'errmsg': "添加邮箱失败"})

        # 发送邮件验证邮件
        verify_url = generate_verify_email(request.user)
        send_verify_email.delay(email, verify_url)

        # 返回响应
        return http.JsonResponse({'code': RETCODE.OK, 'errmsg': "添加邮箱成功"})


class UserInfoView(LoginRequiredMixin, View):
    """用户中心"""

    def get(self, request):
        """提供用户个人信息页面"""
        context = {
            'username': request.user.username,
            'mobile': request.user.mobile,
            'email': request.user.email,
            'email_active': request.user.email_active,
        }
        return render(request, 'user_center_info.html', context)


class LogoutView(View):
    """用户退出"""

    def get(self, request):
        """实现用户退出"""
        # 清除状态保持信息
        logout(request)

        # 重定向到首页
        response = redirect(reverse('contents:index'))

        # 删除用户登陆cookie信息中的用户名
        response.delete_cookie('username')

        # 返回响应
        return response


class LoginView(View):
    """用户登陆"""

    def get(self, request):
        """提供用户登陆页面"""
        return render(request, 'login.html')

    def post(self, request):
        """实现用户登陆逻辑"""
        # 接收参数
        username = request.POST.get('username')
        password = request.POST.get('password')
        remembered = request.POST.get('remembered')
        # 校验参数
        if not all([username, password]):
            # return '如果缺少必传参数，响应错误提示信息，403'
            return http.HttpResponseForbidden("缺少参数")

        if not re.match(r'^[a-zA-Z0-9_-]{5,20}$', username):
            return http.HttpResponseForbidden("请输入正确的用户名或者密码")

        if not re.match(r'^[a-zA-Z0-9_-]{5,20}$', password):
            return http.HttpResponseForbidden("请输入8-20个字符")

        # 认证用户:使用账号查询用户是否存在，若用户存在则校验密码是否正确
        # user = User.objects.get(username=username)
        # user.check_password()
        user = authenticate(username=username, password=password)
        if user is None:
            return render(request, 'login.html', {'account_errmsg': '账号或密码错误'})

        # 状态保持
        login(request, user)
        # 使用remembered确定状态保持的周期(实现记住登陆)
        if remembered != 'on':
            # 没有记住登陆：状态保持在浏览器会话结束就销毁
            request.session.set_expiry(0)  # 单位是秒
        else:
            # 记住登陆状态保持状态周期为两个星期
            request.session.set_expiry(None)

        # 响应结果
        # 取出next
        next = request.GET.get('next')
        if next:
            response = redirect(next)
        else:
            response = redirect(reverse('contents:index'))

        # 为了实现用户登陆状态保持，将用户名添加到cookie中
        response.set_cookie('username', user.username, max_age=3600 * 24 * 15)

        # 效应结果
        return response


class UsernameCountView(View):
    """用户名是否重复注册"""

    def get(self, request, username):
        """
        :param 请求体:
        :param 用户名:
        :return:  JSON
        """
        # 接收和校验参数
        # 实现主体业务逻辑，使用username查询对应的记录的条数 filter返回的是满足条件的结果集
        # 查询满足条件的条数
        count = User.objects.filter(username=username).count()

        # 响应结果
        return http.JsonResponse({'code': RETCODE.OK, 'errmsg': 'OK', 'count': count})


class MobileCountView(View):
    """判断手机号是否被重复注册"""

    def get(self, request, mobile):
        count = User.objects.filter(mobile=mobile).count()
        return http.JsonResponse({'code': RETCODE.OK, 'errmsg': 'OK', 'count': count})


class RegisterView(View):
    # 用户注册
    def get(self, request):
        # 提供数据(提供用户注册页面)
        return render(request, 'register.html')

    def post(seif, request):
        """用户注册"""
        # 接收数据 提取参数
        username = request.POST.get('username')
        password = request.POST.get('password')
        password2 = request.POST.get('password2')
        mobile = request.POST.get('mobile')
        sms_code_client = request.POST.get('sms_code')
        allow = request.POST.get('allow')

        # 校验参数:前后端校验需要分开，要保证后端安全，前后端的逻辑相同
        # 判断参数是否齐全
        # all方法会去校验列表中的元素是否为空，只要有一个为空就返回false
        if not all([username, password, password2, mobile, allow]):
            # return '如果缺少必传参数，响应错误提示信息，403'
            return http.HttpResponseForbidden("缺少参数")
        # 判断用户名是否是5-20个字符
        if not re.match(r'^[a-zA-Z0-9_-]{5,20}$', username):
            return http.HttpResponseForbidden("请输入5-20个字符")
        # 判断密码是否是8-20个字符
        if not re.match(r'^[a-zA-Z0-9_-]{5,20}$', password):
            return http.HttpResponseForbidden("请输入8-20个字符")
        # 判断两次密码是否一致
        if password != password2:
            return http.HttpResponseForbidden("两次输入的密码不一致")
        # 判断手机号是否合法
        if not re.match(r'^1[3-9]\d{9}$', mobile):
            return http.HttpResponseForbidden("请输入正确的手机号码")

        # 判断短信验证码是否输入正确
        redis_conn = get_redis_connection('verify_code')
        sms_code_server = redis_conn.get('sms_%s' % mobile)
        # 判断短信验证码是否存在
        if sms_code_server is None:
            return render(request, 'register.html', {'sms_code_errmsg': '短信验证码已失效'})
        # 判断输入的短信验证码是否和Redis中的短信验证码是否一致
        if sms_code_client != sms_code_server.decode():
            return render(request, 'register.html', {'sms_code_errmsg': '短信验证码有误'})
        # 判断用户是否勾选协议
        if allow != 'on':
            return http.HttpResponseForbidden("请勾选用户协议")
        # 测试
        # return render(request, "register.html", {'register_errmsg': '注册失败'})

        # 保存注册数据，注册业务的核心
        try:
            user = User.objects.create_user(username=username, password=password, mobile=mobile)
        except DatabaseError:
            return render(request, "register.html", {'register_errmsg': '注册失败'})

        # 状态保持
        login(request, user)

        # 响应结果 重定向到首页
        # return redirect('/')
        # 响应结果
        response = redirect(reverse('contents:index'))

        # 为了实现用户登陆状态保持，将用户名添加到cookie中
        response.set_cookie('username', user.username, max_age=3600 * 24 * 15)

        # 效应结果
        return response
